package ie.lawlor.amvc;

import ie.lawlor.amvc.event.Event;
import ie.lawlor.amvc.event.EventName;
import ie.lawlor.amvc.event.EventNotHandledException;
import ie.lawlor.amvc.event.IEventHandler;

import java.io.Serializable;

import org.apache.log4j.Logger;


/**
 * @author blawlor
 * Starting point for all Models. Defines some basic utility methods
 * and properties.
 */
public class AbstractModel implements IModel, Serializable {
	
	protected Logger logger;
	private IController controller;
	private IEventHandler eventHandler;
	protected String name;
	protected String loggerName;
  /**
   * True if there is an outstanding event to be processed, which wasn't because 
   * corresponding view was not visible when event was received.
   */
  private boolean outOfSynch = false;
  /**
   * Outstanding event to be processed. Null if none.
   * TODO Consider replacing with queue, and finding ways to remove outstanding events.
   */
  private Event lastSynchEvent;
	
	protected AbstractModel(String name, IEventHandler eventHandler){
		this.name = name;
		initLogger(name);
		this.eventHandler = eventHandler;
		this.eventHandler.setLoggerName(name);
		this.eventHandler.setEventSink(this);
	}
	
	private void initLogger(String name){
		loggerName = "hmvc." + name + ".model";
		logger = Logger.getLogger(loggerName);
	}

	protected boolean isVisible(){
		return controller.isVisible();
	}
	
	
	/**
	 * Call this at the start of a do[Event] method to see if you can defer doing 
	 * potentially 'heavy' work in your model until the corresponding view is visible (or never
	 * if this doesn't come about). The resynchronize method is called to process the deferred
	 * event, but this method can be overridden by derived classes. Do not call this method in 
	 * do[Event] methods which must be called before the resynchronize method itself can be 
	 * successfully called.   
	 * @return true if you must peform this now.
	 * TODO See if there is a way to avoid the derived class hving to call this explicitly. Perhaps 
	 * add this code to the handleEvent method. 
	 */
	public boolean isEventToBeProcessedNow(Event event){
	  logger.debug("Entering");
	  if (isVisible()){
	    return true;
	  } else {
	    lastSynchEvent = event;
	    setOutOfSynch(true);
	    return false;
	  }
	}
	
	public void visibilityUpdated(boolean visible){
	  logger.info("Visibility updated (" + visible + ") " + getClass());
	  if (visible && isOutOfSynch()){
	    resynchronize();
	    setOutOfSynch(false);
	  }
	}
  
  public void doCursorNormal(Event e) {
      fire(e);
  }

  public void doCursorWaiting(Event e) {
  	fire(e);
  }

	public void setController(IController controller){
		this.controller = controller;
	} 
	
	protected IController getController(){
		return controller;
	}
	
	public void fire(Event e){
		logger.info("Firing " + e);
		e.setSourceController(controller);
		controller.eventIn(e);
	}
	
	public void fire(EventName name){
		fire(name, null);
	}
	
	public void fire(EventName name, Object payload){
		logger.debug("Firing " + name);
		Event e = new Event(name, this, payload);
		e.setSourceController(controller);
		controller.eventIn(e);
	}
	
    public void eventIn(Event e) {
		logger.debug("Entering"); 
    	System.out.println("Model for " + name + "(" + getClass() +	"): Handling event " + e);
		try {
			eventHandler.handleEvent(e);
		} catch (EventNotHandledException ex){
			System.out.println("Warning: Event " + e + " not handled by class " + getClass());
			logger.warn("Event " + e + " was not handled by class " + getClass());
		}
    }

    protected void setOutOfSynch(boolean outOfSynch) {
    	logger.debug("Entering");
    	this.outOfSynch = outOfSynch;
    }

    /**
     * @return
     */
    protected boolean isOutOfSynch() {
    	return outOfSynch;
    }

    public void resynchronize() {
      logger.info("Resynchronizing " + getClass());
    	eventIn(lastSynchEvent);
    }    
}
